package org.myminidao.dao;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.myminidao.MyDaoFactory;
import org.myminidao.conf.pojo.DaoPojo;
import org.myminidao.conf.pojo.ParamPojo;
import org.myminidao.conf.pojo.SqlMapPojo;
import org.myminidao.conf.pojo.SqlPojo;
import org.myminidao.enums.MyJDBCType;
import org.myminidao.enums.SqlType;
import org.myminidao.utils.SqlUtil;

public class DaoProxy<T> implements InvocationHandler {
	
	private Connection connection;
	
	private Class<T> daoInterface;

	public DaoProxy(Connection connection, Class<T> daoInterface) {
		super();
		this.connection = connection;
		this.daoInterface = daoInterface;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
		Map<String, DaoPojo> map = MyDaoFactory.getDaoMap();
		DaoPojo daoPojo = map.get(daoInterface.getName());
		if (null == daoPojo) {
			return null;
		}
		String methodName = arg1.getName();
		SqlPojo sqlPojo = daoPojo.getMap().get(methodName);
		String sqlFilePath = sqlPojo.getSqlFilePath();
		
		
		List<ParamPojo> paramList = sqlPojo.getParamList();
		Map<String, Object> paramMap = new HashMap<String, Object>();
		if (null != paramList && 0 != paramList.size()) {
			for (int i = 0; i < arg2.length; i++) {
				Object object = arg2[i];
				ParamPojo paramPojo = paramList.get(i);
				paramMap.put(paramPojo.getName(), object);
			}
		}
		String sql = SqlUtil.getSql(sqlFilePath, sqlPojo.getMethodFileName() + ".sql", paramMap);
		System.out.println(sql);
		Statement statement = connection.createStatement();
		
		SqlType type = sqlPojo.getType();
		if (type.equals(SqlType.UPDATE) || type.equals(SqlType.INSERT) || type.equals(SqlType.DELETE)) {
			int num = statement.executeUpdate(sql);
			return new Integer(num);
		}
		if (type.equals(SqlType.SELECT)) {
			ResultSet rs = statement.executeQuery(sql);
			if (sqlPojo.getIsList()) {
				List list = new ArrayList();
				String returnClass = sqlPojo.getReturnClass();
				if (null == returnClass || 0 == returnClass.length()) {
					MyJDBCType jdbcType = sqlPojo.getReturnJdbcType();
					while(rs.next()) {
						switch(jdbcType) {
						case STRING:
							list.add(rs.getString(1));
							continue;
						case INTEGER:
							list.add(rs.getInt(1));
							continue;
						case FLOAT:
							list.add(rs.getFloat(1));
							continue;
						case DOUBLE:
							list.add(rs.getDouble(1));
							continue;
						case TIME:
							Date date = rs.getDate(1);
							if (null != date) {
								list.add(new java.util.Date(date.getTime()));
							}
							continue;
						default:
							break;
						}
					}
				} else {
					List<SqlMapPojo> mapList = sqlPojo.getSqlMapList();
					Class clazz = Class.forName(returnClass);
					while(rs.next()) {
						Object obj = clazz.newInstance();
						for (SqlMapPojo sqlMapPojo : mapList) {
							String column = sqlMapPojo.getColumn();
							String fieldName = sqlMapPojo.getVariableName();
							MyJDBCType returnType = sqlMapPojo.getJdbcType();
							
							Field field = clazz.getDeclaredField(fieldName);
							PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
							Method setMethod = pd.getWriteMethod();
							
							switch(returnType) {
							case STRING:
								setMethod.invoke(obj, rs.getString(column));
								continue;
							case INTEGER:
								setMethod.invoke(obj, rs.getInt(column));
								continue;
							case FLOAT:
								setMethod.invoke(obj, rs.getFloat(column));
								continue;
							case DOUBLE:
								setMethod.invoke(obj, rs.getDouble(column));
								continue;
							case TIME:
								Date date = rs.getDate(column);
								if (null != date) {
									setMethod.invoke(obj, new java.util.Date(date.getTime()));
								}
								continue;
							default:
								break;
							}
						}
						list.add(obj);
						
					}
				}
				
				return list;
			} else {
				if (rs.next()) {
					String returnClass = sqlPojo.getReturnClass();
					if (null == returnClass || 0 == returnClass.length()) {
						MyJDBCType jdbcType = sqlPojo.getReturnJdbcType();
						switch(jdbcType) {
						case STRING:
							return rs.getString(1);
						case INTEGER:
							return rs.getInt(1);
						case FLOAT:
							return rs.getFloat(1);
						case DOUBLE:
							return rs.getDouble(1);
						case TIME:
							Date date = rs.getDate(1);
							if (null != date) {
								return new java.util.Date(date.getTime());
							}
						default:
							break;
						}
					} else {
						List<SqlMapPojo> mapList = sqlPojo.getSqlMapList();
						Class clazz = Class.forName(returnClass);
						Object obj = clazz.newInstance();
						for (SqlMapPojo sqlMapPojo : mapList) {
							String column = sqlMapPojo.getColumn();
							String fieldName = sqlMapPojo.getVariableName();
							MyJDBCType returnType = sqlMapPojo.getJdbcType();
							
							Field field = clazz.getDeclaredField(fieldName);
							PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
							Method setMethod = pd.getWriteMethod();
							
							switch(returnType) {
							case STRING:
								setMethod.invoke(obj, rs.getString(column));
								break;
							case INTEGER:
								setMethod.invoke(obj, rs.getInt(column));
								break;
							case FLOAT:
								setMethod.invoke(obj, rs.getFloat(column));
								break;
							case DOUBLE:
								setMethod.invoke(obj, rs.getDouble(column));
								break;
							case TIME:
								Date date = rs.getDate(column);
								if (null != date) {
									setMethod.invoke(obj, new java.util.Date(date.getTime()));
								}
								break;
							default:
								break;
							}
						}
						
						return obj;
					}
				} else {
					return null;
				}
			}
			
		}
		
		Object ret = null;
		return ret;
	}

}
